package org.arkeologgr.appening.util;

import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.concurrent.RejectedExecutionException;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.widget.ArrayAdapter;
import android.widget.ImageView;

/**
 * An static class for loading images asynchronously for 
 * stand alone ImageViews or ImageView contained in ListView
 * elements
 * 
 * The ImageLoader keeps loaded images in a cache with soft
 * references which will be reclaimed by the Garbage 
 * Collector when no other object references them.  
 */
public class ImageLoader {

	/*
	 * Soft-referenced image cache. holds loaded images 
	 */
	static LinkedHashMap<String, SoftReference<Bitmap>> imageCache = new LinkedHashMap<String, SoftReference<Bitmap>>();

	/*
	 * the place holder bitmap is used as temporary entry in 
	 * the image cache while a real image for a URL is being loaded.
	 */
	static Bitmap placeHolderBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565);
	static SoftReference<Bitmap> placeHolderReference = new SoftReference<Bitmap>(placeHolderBitmap);
	
	/**
	 * Get an image for an ImageView. Will use cached image if possible,
	 * otherwise start a asynchronous fetch of the image for the ImageView
	 */
	public static void getImage(ImageView icon, String imageUrl) {
		getImage(icon, imageUrl, true);
	}
	
	/**
	 * Get an image for an ImageView. Will use cached image if possible,
	 */
	public static void getImage(ImageView icon, String imageUrl, boolean allowAsyncFetch) {
		
		if (!trySetImage(icon, imageUrl)) {
			if (allowAsyncFetch) {
				imageCache.put(imageUrl, placeHolderReference);
				new LoadImageInBackground().execute(new Object[] { icon, imageUrl });
			}
		}
	}
	/**
	 * Get an image for an ImageView. Will use cached image if possible,
	 * otherwise start a asynchronous fetch of the image and notify the
	 * ArrayAdapter on completion.
	 */
	public static <T> void getImage(ArrayAdapter<T> arrayAdapter, ImageView icon, String imageUrl) {
		
		if (!trySetImage(icon, imageUrl)) {
			imageCache.put(imageUrl, placeHolderReference);
			try {
			new LoadListImageInBackground<T>().execute(new Object[] {arrayAdapter, imageUrl});
			} catch (RejectedExecutionException rej) {
				// too many threads queued already. Current implementation does not handle
				// this case well. do nothing and leave the cache as we found it...
				imageCache.remove(imageUrl);
			}
		}
	}

	/**
	 * Tries to set the Bitmap image of an ImageView image for a URL key.
	 * 
	 * @return true if the image is in the cache and can be set _or_ is in
	 * pending state (downloading or queued to download, so it will be set soon)
	 */
	private static boolean trySetImage(ImageView icon, String imageUrl) {
		
		if (imageCache.containsKey(imageUrl)) {
			Bitmap image = imageCache.get(imageUrl).get();
			if (image != null && image != placeHolderBitmap) {
				// image is in cache
				icon.setImageBitmap(image);
				return true;
			} else if (image == placeHolderBitmap) {
				// loading in progress
				return true;
			}
		} 

		return false;
	}

	/**
	 * AsyncTask for loading an image. Sets the loaded
	 * image in the supplied ImageView on completion.
	 */
	static class LoadImageInBackground extends AsyncTask<Object, Void, Bitmap> {
		ImageView mIcon = null;
		String mUrl = null;

		@Override
		protected Bitmap doInBackground(Object... params) {
			mIcon = (ImageView) params[0];
			mUrl = (String) params[1];
			return loadImage(mUrl);
		}

		@Override
		protected void onPostExecute(Bitmap result) {
			super.onPostExecute(result);
			mIcon.setImageBitmap(result);
			imageCache.put(mUrl, new SoftReference<Bitmap>(result));
		}
	}
	
	/**
	 * AsyncTask for loading an image somewhere in a ListView 
	 * item. Notifies the supplied ListAdapter on completion.
	 */
	static class LoadListImageInBackground<T> extends AsyncTask<Object, Void, Bitmap> {

		String mUrl = null;
		ArrayAdapter<T> mAdapter = null;
		
		@SuppressWarnings("unchecked")
		@Override
		protected Bitmap doInBackground(Object... params) {
			mAdapter = (ArrayAdapter<T>) params[0];
			mUrl = (String) params[1];
			return loadImage(mUrl);
		}

		@Override
		protected void onPostExecute(Bitmap result) {
			super.onPostExecute(result);
			mAdapter.notifyDataSetChanged();
			imageCache.put(mUrl, new SoftReference<Bitmap>(result));
		}
	}
	
	/**
	 * Loads and decodes a bitmap from an URL
	 */
	private static Bitmap loadImage(String urlString) {
		
		// validate url
		URL imageUrl = null;
		try {
			imageUrl = new URL(urlString);
		} catch (MalformedURLException error) {
			error.printStackTrace();
			return null;
		}

		// fetch image data from url
		InputStream input = null;
		try {
			HttpURLConnection connection = (HttpURLConnection) imageUrl
					.openConnection();
			connection.setDoInput(true);
			connection.connect();
			input = connection.getInputStream();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		// return the loaded image
		Bitmap image = BitmapFactory.decodeStream(input);
		return image;
	}

	
}